%{
/*********************************************
将所有的词法分析功能均放在 yylex 函数内实现，为 +、-、*、\、(、 ) 每个运算符及整数分别定义一个单词类别，在 yylex 内实现代码，能
识别这些单词，并将单词类别返回给词法分析程序。
实现功能更强的词法分析程序，可识别并忽略空格、制表符、回车等
空白符，能识别多位十进制整数。
YACC file
**********************************************/

/*
一：代码设计思路 预想
1.定义词法分析器中识别的单词类别，如"+"、"-"、"*"、"/"、"("、")"以及数字和标识符。
2.定义词法分析器函数yylex()，在该函数中实现了对输入字符的识别和判断，根据不同的字符返回对应的单词类别。 
3.定义语法规则，并且为每个非终结符定义了相应的属性值类型。 
4.定义语法分析器函数yyerror()，用于在分析过程中发生错误时输出错误信息。
5.定义符号表查找函数symlook()，用于在符号表中查找标识符并返回对应的表项指针。
*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>  //导入isdigit()函数
#include <string.h> 
#define NSYMS 20        /* maximum number of symbols */
struct symtab {
    char *name;         
    double value;       
} symtab[NSYMS];       
struct symtab *symlook(char *s);    
char idStr[50];                     
int yylex();                       
extern int yyparse();              
FILE* yyin;    
void yyerror(const char* s);    //错误处理函数

%}

//属性值可能具有的类型
%union {
    double  dval;    
    struct symtab *symp;    
}

//%token 为每一个都自动分配不等的整数值
%token ADD      
%token SUB
%token MUL
%token DIV
%token LEFTPAR
%token RIGHTPAR
%token <dval> NUMBER    
%token UMINUS
%token equal
%token <symp> ID    

%right equal
%left ADD SUB
%left MUL DIV
%right UMINUS
%left LEFTPAR RIGHTPAR

//表达式的属性值设置为数值类型
%type <dval> expr

%%

lines    :    lines expr ';'    { printf("%f\n", $2); }    //表达式后面有分号，则输出表达式的值
            | lines ';'    //只有分号的行
            |    ;    //空行
expr    :    expr ADD expr { $$ = $1 + $3; }    //加法表达式
        |    expr SUB expr { $$ = $1 - $3; }    //减法表达式
        |    expr MUL expr { $$ = $1 * $3; }    //乘法表达式
        |    expr DIV expr { $$ = $1 / $3; }    //除法表达式
        |    LEFTPAR expr RIGHTPAR { $$ = $2; }    //括号内的表达式
        |    UMINUS expr %prec UMINUS { $$ = -$2; }    //负数表达式
        |    NUMBER { $$ = $1; }    //数字
        |    ID equal expr { $1->value = $3; $$ = $3; }    //标识符的赋值表达式
        |    ID { $$ = $1->value; }    //标识符引用表达式
        ;


%%

int isLastCharPram = 0;
int count = 0;
int yylex()
{
    int t;
    count++;
    while (1)
    {
        t = getchar();
        if (t == ' ' || t == '\n' || t == '\t') { ; }    //忽略空格、换行和制表符
        else if (isdigit(t))    //如果是数字
        {
            yylval.dval = 0;    //属性值，在此处即指对应的数值
            while (isdigit(t))
            {
                yylval.dval = yylval.dval * 10 + t - '0';    //计算数值
                t = getchar();    //继续读取下一个字符
            }
            ungetc(t, stdin);    //将读取到的多余字符放回输入流
            isLastCharPram = 0;
            return NUMBER;
        }
        else if ((t >= 'a'&&t <= 'z') || (t >= 'A'&&t <= 'Z') || (t == '_'))    //标识符以字母下划线开头
        {
            int ti = 0;
            while ((t >= 'a'&&t <= 'z') || (t >= 'A'&&t <= 'Z') || (t == '_') || (t >= '0'&&t <= '9'))
            {
                idStr[ti] = t;    //记录标识符的字符
                t = getchar();    //继续读取下一个字符
                ti++;
            }
            idStr[ti] = '\0';    //加上字符串结束符

            char* temp = (char*)malloc(50 * sizeof(char));   
            strcpy(temp, idStr);    //将标识符的字符串拷贝到临时数组中
            yylval.symp = symlook(temp);    //在符号表中查找对应的标识符

            ungetc(t, stdin);    //将读取到的多余字符放回输入流
            isLastCharPram = 0;
            return ID;
        }
        else if (t == '-')    //处理负数的情况
        {
            if (count != 1 && isLastCharPram == 0)
            {
                return SUB;
            }
            else
            {
                isLastCharPram = 0;
                return UMINUS;
            }
        }
        else if (t == '(')
        {
            isLastCharPram = 1;
            return LEFTPAR;
        }
        else if (t == ';')
        {
            count = 0;   // 每次读入一个字符串重新清0
            return t;
        }
        else if (t == '=')
        {
            isLastCharPram = 0;
            return equal;
        }
        else
        {
            isLastCharPram = 0;
            if (t == '+') return ADD;
            else if (t == '*') return MUL;
            else if (t == '/') return DIV;
            else if (t == ')') return RIGHTPAR;
            else return t;
        }
    }
}

int main()
{
    yyin = stdin;    //设置输入为标准输入
    do {
        yyparse();    //进行语法分析
    } while (!feof(yyin));
    return 0;
}

void yyerror(const char* s) {
    fprintf(stderr, "Parse error: %s\n", s);
    exit(1);
}

// 符号表查找程序
// 返回一个符号表的表项
struct symtab* symlook(char *s){
    char *p;
    struct symtab *sp;
    // 遍历符号表中每一个表项
    for(sp=symtab; sp<&symtab[NSYMS]; sp++){
        // 如果找到了表中存在这一项，返回指向该表项的指针
        if(sp->name && !strcmp(sp->name,s))
            return sp;
        // 如果找不到这一项，那就新建一个表项，strdup函数用于拷贝字符串
        if(!sp->name){
            sp->name = strdup(s);
            return sp;
        }
    }
    yyerror("Wrong input");
    exit(1);
}
